package uk.ac.sanger.das.dasregistry.springrest.sources;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Date;
import java.util.Enumeration;
import java.util.List;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.log4j.Logger;
import org.biodas.jdas.dassources.utils.DasSourceComparator;
import org.biodas.jdas.dassources.utils.DasSpec;
import org.biodas.jdas.dassources.utils.DasTimeFormat;
import org.biodas.jdas.dassources.utils.RegistrySourceAdapter;
import org.biodas.jdas.dassources.utils.SourcesFilter;
import org.biodas.jdas.schema.lastmodified.LASTMODIFIED;
import org.biodas.jdas.schema.registry.capabilities.CAPABILITIES;
import org.biodas.jdas.schema.servers.SERVERS;
import org.biodas.jdas.schema.sources.SOURCE;
import org.biodas.jdas.schema.sources.SOURCES;
import org.biodas.jdas.servlet.utils.DASQueryStringTranslator;
import org.biodas.jdas.servlet.utils.RowsFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Controller;
import org.springframework.util.ClassUtils;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;

import uk.ac.sanger.das.dasregistry.springrest.security.Authenticate;
import uk.ac.sanger.das.dasregistry.springrest.security.AuthenticationDAO;
import uk.ac.sanger.solr.GetResultsFromSanger;

/**
 * A controller handling requests for CRUD operations on DasSources.
 */
@Controller
public class SourcesController {
	Authenticate authenticate;
	static Logger logger = Logger.getLogger(SourcesController.class);
	private DasSourceManager dasSourceManager;
	private String regPrefix = "DS_";

	/**
	 * Creates a new DasSourceController with a given DasSource manager.
	 */
	@Autowired
	public SourcesController(DasSourceManager dasSourceManager,
			Authenticate authenticate) {
		this.dasSourceManager = dasSourceManager;
		this.authenticate = authenticate;
	}

	@RequestMapping(value = "/sources/activate/DS_{sourceUri}", method = { RequestMethod.PUT })
	@ResponseStatus(HttpStatus.OK)
	public void activateSource(@PathVariable("sourceUri") String sourceUri) {

		dasSourceManager.activate("DS_" + sourceUri);
	}

	@RequestMapping(value = "/knownCapabilities", method = { RequestMethod.GET })
	@ResponseStatus(HttpStatus.OK)
	public @ResponseBody
	CAPABILITIES capabilities(HttpServletRequest req, HttpServletResponse resp) {

		return dasSourceManager.getKnownCapabilities();

	}

	/**
	 * Handle post requests for adding a source (restrict it to one source at a
	 * time so we can send useful errors back and keep the registry very simple
	 * 
	 * @param req
	 *            with body containing the source to be added
	 * @param resp
	 *            with body containing the source that was successfully added
	 *            and their registry ids if new ids are to be assigned (as in
	 *            the case of the main sanger registry)
	 * @return
	 * @throws DasSourceNotRegisterableExcepton
	 */
	@RequestMapping(value = "/sources/", method = { RequestMethod.PUT })
	public @ResponseBody
	SOURCES update(@RequestBody SOURCES sources, HttpServletRequest req,
			HttpServletResponse resp) throws DasSourceNotRegisterableExcepton {
		logger.warn("request to update source "
				+ sources.getSOURCE().get(0).getUri());
		Enumeration e = req.getHeaderNames();
		while (e.hasMoreElements()) {
			String key = (String) e.nextElement();
			String headerValue = req.getHeader(key);
			System.out.println("key:" + key + "|" + headerValue);

		}
		SOURCES addedSources = new SOURCES();
		if (authenticated(req)) {
			try {
				addedSources = dasSourceManager.update(sources);
				resp.setStatus(HttpStatus.ACCEPTED.value());
			} catch (DasSourceNotRegisterableExcepton e1) {
				e1.printStackTrace();
				// need to set the response status here otherwise no client side
				// error will be thrown!
				resp.setStatus(HttpStatus.NOT_ACCEPTABLE.value());
				return addedSources;// return empty sources
			}
		} else {
			resp.setStatus(HttpStatus.UNAUTHORIZED.value());
			return addedSources;
		}
		return addedSources;
	}

	// other controller method omitted

	 @ExceptionHandler(DasSourceNotRegisterableExcepton.class)
	 public String
	 dASSourceNotRegisterableExcepton(DasSourceNotRegisterableExcepton ex,
	 HttpServletRequest request, HttpServletResponse resp) {
	 resp.setStatus(401);
	 return ClassUtils.getShortName(ex.getMessage());
	 }

	private boolean authenticated(HttpServletRequest req) {
		//authenticate=null;//just put here for testing please take out
		if (authenticate == null) {
			// if no authentication then we assume the setup doesn't want any
			// and we just say yes to every request
			logger.warn("auth is null");
			return true;
		}
		String auth = req.getHeader("authorization");

		logger.debug("authorization " + auth);

		String isSecure = "false";// should set this by config so ignore on dev,
									// enforce on live. previously done through
									// servlet context
		if (isSecure.equals("true")) {
			// if not secure ie. on mac then just don't do any of this https
			// checking
			// if we are on live or sandbox isSecure should be true then lets
			// check the protocol is https
			// do this by checking the header X-is-ssl=1 which is set by the
			// front ends
			String sslHeader = req.getHeader("X-is-ssl") + "";
			if (!sslHeader.equals("1")) {
				System.out
						.println("Needs to be an https request for this request");
				return false;
			}

		}

		if (authenticate.authenticate(auth)) {
			logger.warn("allowed");
			return true;
		}
		// need to test user and password here using and authentication method -
		// interface here?
		return false;
	}

	/**
	 * Handle post requests for adding a source (restrict it to one source at a
	 * time so we can send useful errors back and keep the registry very simple
	 * 
	 * @param req
	 *            with body containing the source to be added
	 * @param resp
	 *            with body containing the source that was successfully added
	 *            and their registry ids if new ids are to be assigned (as in
	 *            the case of the main sanger registry)
	 * @return
	 */
	@RequestMapping(value = "/lastModified", method = { RequestMethod.GET })
	@ResponseStatus(HttpStatus.OK)
	public @ResponseBody
	LASTMODIFIED lastModified(HttpServletResponse resp) {
		// resp.setHeader("Content-Encoding","ISO-8859-1");
		// resp.setHeader("Content-Type", "application/xml");
		// //probably need to accept post in the listSources method instead and
		// fork from there
		// logger.debug("request to lastModified ");
		// String
		// lastModifiedString="<?xml version='1.0' encoding='UTF-8' ?>/n"+"<LASTMODIFIED time=\""+DasTimeFormat.toDASString(dasSourceManager.getLastModified())+"\"/>";
		// return lastModifiedString;
		return dasSourceManager.getLastModified();

	}

	/**
	 * Handle post requests for adding a source (restrict it to one source at a
	 * time so we can send useful errors back and keep the registry very simple
	 * 
	 * @param req
	 *            with body containing the source to be added
	 * @param resp
	 *            with body containing the source that was successfully added
	 *            and their registry ids if new ids are to be assigned (as in
	 *            the case of the main sanger registry)
	 * @return
	 */
	@RequestMapping(value = "/sources/", method = { RequestMethod.POST })
	// @ResponseStatus(HttpStatus.CREATED)
	public @ResponseBody
	SOURCES add(@RequestBody SOURCES sources, HttpServletRequest req,
			HttpServletResponse resp) {

		// probably need to accept post in the listSources method instead and
		// fork from there
		logger.warn("request to add sources via post sources "
				+ sources.getSOURCE().get(0).getUri());
		SOURCES createdSources = null;

		if (authenticated(req)) {
			try {
				createdSources = dasSourceManager.add(sources);
			} catch (DasSourceNotRegisterableExcepton e) {
				//  Auto-generated catch block
				e.printStackTrace();
				// need to set the response status here otherwise no client side
				// error will be thrown!
				resp.setStatus(HttpStatus.NOT_ACCEPTABLE.value());
				PrintWriter writer = null;
				try {
					writer = resp.getWriter();
				} catch (IOException e1) {
					//  Auto-generated catch block
					e1.printStackTrace();
				}
				writer.write(e.getMessage());
				return null;
			}
			resp.setStatus(HttpStatus.CREATED.value());
		} else {
			resp.setStatus(HttpStatus.UNAUTHORIZED.value());
			PrintWriter writer = null;
			try {
				writer = resp.getWriter();
			} catch (IOException e1) {
				//  Auto-generated catch block
				e1.printStackTrace();
			}
			writer.write("Unauthorized");
			return null;
		}
		return createdSources;
	}

	// @ExceptionHandler(DASSourceNotValidExcepton.class)
	// public String handleIOException(DASSourceNotValidExcepton ex,
	// HttpServletRequest request)
	// {
	// System.out.println("caught using spring exception handler method");
	// return ClassUtils.getShortName(ex.getClass());
	// }

	/**
	 * Handle post requests for adding a source (restrict it to one source at a
	 * time so we can send useful errors back and keep the registry very simple
	 * 
	 * @param req
	 *            with body containing the source to be added
	 * @param resp
	 *            with body containing the source that was successfully added
	 *            and their registry ids if new ids are to be assigned (as in
	 *            the case of the main sanger registry)
	 * @return
	 */
	@RequestMapping(value = "/sources/DS_{sourceUri}", method = { RequestMethod.DELETE })
	@ResponseStatus(HttpStatus.OK)
	public void delete(@PathVariable("sourceUri") String sourceUri,
			HttpServletRequest req, HttpServletResponse resp) {

		// probably need to accept post in the listSources method instead and
		// fork from there
		logger.debug("request to delete sources via delete " + sourceUri);
		if (authenticated(req)) {
			dasSourceManager.delete("DS_" + sourceUri);
		} else {
			resp.setStatus(HttpStatus.UNAUTHORIZED.value());
			PrintWriter writer = null;
			try {
				writer = resp.getWriter();
			} catch (IOException e1) {
				//  Auto-generated catch block
				e1.printStackTrace();
			}
			writer.write("Unauthorized");
			return;
		}
		return;
	}

	/**
	 * Provide a list of all DasSources.
	 */

	@RequestMapping(value = "/sources/inactive*", method = { RequestMethod.GET,
			RequestMethod.HEAD })
	@ResponseStatus(HttpStatus.OK)
	public @ResponseBody
	SOURCES listInactiveSources(HttpServletRequest req, HttpServletResponse resp) {
		String active = "false";
		return writeSources(req, resp, active);

	}

	/**
	 * Provide a list of all DasSources.
	 */

	@RequestMapping(value = "/sources/all*", method = { RequestMethod.GET,
			RequestMethod.HEAD })
	@ResponseStatus(HttpStatus.OK)
	public @ResponseBody
	SOURCES listAllSources(HttpServletRequest req, HttpServletResponse resp) {
		String active = "any";
		return writeSources(req, resp, active);

	}

	/**
	 * Provide a list of active das sources same as default sources request.
	 */

	@RequestMapping(value = "/sources/active*", method = { RequestMethod.GET,
			RequestMethod.HEAD })
	@ResponseStatus(HttpStatus.OK)
	public @ResponseBody
	SOURCES listActiveSources(HttpServletRequest req, HttpServletResponse resp) {
		String active = "true";
		return writeSources(req, resp, active);

	}

	/**
	 * Provide a list of all DasSources.
	 */

	@RequestMapping(value = "/sources*", method = { RequestMethod.GET,
			RequestMethod.HEAD })
	@ResponseStatus(HttpStatus.OK)
	public @ResponseBody
	SOURCES listSources(HttpServletRequest req, HttpServletResponse resp) {
		String active = "true";
		return writeSources(req, resp, active);

	}

	private SOURCES writeSources(HttpServletRequest req,
			HttpServletResponse resp, String active) {
		// We need this here as tomcat doesn't seperate params by ; by default
		// so we have to alter the request
		req = new DASQueryStringTranslator(req);
		setHeaders(resp);
		logger.info("get request for sources " + req.getQueryString());
		// as we are only dealing with /sources urls we can just use an old
		// modified displayDas1Sources method
		// System.out.println("capabs new="+capabsnew);

		boolean displayTimes = false;
		String times = req.getParameter("times");
		// System.out.println("times=" + times);
		if (times != null) {
			if (times.equals("true"))
				displayTimes = true;
		}

		// String active=req.getParameter("active");
		// if(active==null){
		// active="true";
		// }

		List<String> labels = getParameterValuesAsListFor(req, "label");

		List<String> coordinateId = getParameterValuesAsListFor(req,
				"coordinateid");
		List<String> organism = getParameterValuesAsListFor(req, "organism");
		List<String> authority = getParameterValuesAsListFor(req, "authority");
		List<String> csName = getParameterValuesAsListFor(req, "CSName");
		List<String> version = getParameterValuesAsListFor(req, "version");// version
																			// of
																			// the
		
		// coordinate system
		// to cope with current registry drop down
		if (csName.size() == 1 && authority.size() == 0 && version.size() == 0) {
			String csNameZero = csName.get(0);
			if (csNameZero != null && csNameZero != "") {
				String versionZero = "";
				String authorityZero = csNameZero;
				if (authorityZero.contains("_")) {
					versionZero = authorityZero.substring(authorityZero
							.indexOf("_") + 1);
					authorityZero = authorityZero.substring(0,
							authorityZero.indexOf("_"));
				}
				authority = new ArrayList();
				authority.add(authorityZero);
				version = new ArrayList();
				version.add(versionZero);

			}
		}

		List<String> type = getParameterValuesAsListFor(req, "type");// type
		// in
		// coordinate
		// system e.g.
		// chromosome
		List<String> csType = getParameterValuesAsListFor(req, "CSTypes");
		if (csType.size() > 0) {
			type = csType;
		}
		// System.out.println("capability in server=" + capability);
		List<String> capabilities = getParameterValuesAsListFor(req,
				"capabilities");
                if(capabilities.isEmpty()){
                    //see if they have specified capabilities as capability
                    capabilities = getParameterValuesAsListFor(req,
				"capability");
                }
		List<String> validCapabilities = getParameterValuesAsListFor(req,
				"validCapabilities");

		String rows = req.getParameter("rows");

		String keywords = req.getParameter("keywords");
		String typeskeywords = req.getParameter("typeskeywords");

		int dasspec = getDasSpecVersionFromURI(req.getRequestURI());
		List<String> specs = getParameterValuesAsListFor(req, "spec");
		// if not spec specified as a parameter then look at the url, default is
		// show all specs
		if (dasspec != -1 && dasspec != 1) {
			DasSpec spec = null;
			if (dasspec == 15) {
				spec = DasSpec.SPEC1_53E;
			} else if (dasspec == 16) {
				spec = DasSpec.SPEC1_6E;
			}
			specs = new ArrayList<String>();
			specs.add(spec.toString());
		}

		List<SOURCE> sources = dasSourceManager.getAllSources().getSOURCE();

		// sources to write is local copy per servlet request so if order
		// changes we aren't changing the order for all susequent reqeusts
		List<SOURCE> sourcesToWrite = new ArrayList<SOURCE>();
		// filter by the searh if keywords have been specified using sanger
		// search results
		if ((keywords != null) && (!keywords.equals(""))) {
			GetResultsFromSanger sanger = new GetResultsFromSanger();
			List<String> ids = sanger.getSourcesForKeywordsSearch(keywords);
			for (SOURCE source : sources) {
				String sourceId = source.getUri();
				// System.out.println(sourceId);
				if (ids.contains(sourceId)) {
					sourcesToWrite.add(source);
				}
			}
		} else if ((typeskeywords != null) && (!typeskeywords.equals(""))) {
			GetResultsFromSanger sanger = new GetResultsFromSanger();
			List<String> ids = sanger
					.getSourcesWithTypesLikeKeywordsSearch(typeskeywords);
			for (SOURCE source : sources) {
				String sourceId = source.getUri();
				if (ids.contains(sourceId))
					sourcesToWrite.add(source);
			}

		} else {
			sourcesToWrite.addAll(sources);
		}
		// // filter by other properities if specified

		SourcesFilter filter = new SourcesFilter();

		List<String> serverUrl = getParameterValuesAsListFor(req, "serverUrl");
		sourcesToWrite = filter.filterBy(sourcesToWrite, labels, organism,
				authority, capabilities, validCapabilities, type, specs,
				version, coordinateId, serverUrl, active);
		logger.info("got #source after filter: " + sourcesToWrite.size());
		//
		// //source for determining if we need to sort and sorting
		//
		String sort = req.getParameter("sort");
		String reverse = req.getParameter("reverse");
		if (sort != null) {
			// sortstring = "&sort=" + sort;
			// sort das sources!
			if (reverse != null) {
				System.out.println("sorted reverse by " + sort);
				// notReverseString += "&reverse=true";

			} else {

				// reverseString += "&reverse=true";
				// System.out.println("reversestring="+reverseString);
			}

			Comparator dsc = DasSourceComparator.fromString(sort);
			java.util.Collections.sort(sourcesToWrite, dsc);
			if (reverse != null) {
				// reverse order
				// java.util.List slist = java.util.Arrays.asList(s);
				logger.debug("reverse not null:" + reverse);
				java.util.Collections.reverse(sourcesToWrite);
				//
			}
		}

		// writeDas1Sources(resp, req, filteredSources, displayTimes);

		SOURCES returnSources = new SOURCES();

		if (rows != null) {
			RowsFilter rowsF = new RowsFilter();
			sourcesToWrite = rowsF.filterByRows(sourcesToWrite, req);
			returnSources.setTotal(rowsF.getTotal());
			returnSources.setStart(rowsF.getStart());
			returnSources.setEnd(rowsF.getStop());

		}
		if (!displayTimes) {
			// stripTimes(sourcesToWrite);
		}
		returnSources.getSOURCE().addAll(sourcesToWrite);
		// PrintWriter writer=null;
		// try {
		// writer = resp.getWriter();
		// } catch (IOException e1) {
		// //  Auto-generated catch block
		// e1.printStackTrace();
		// }
		// writer.println("<?xml version='1.0'?>");
		// writer.println("<?xml-stylesheet type=\"text/xsl\" href=\"\">");
		return returnSources;
	}

	public static void setHeaders(HttpServletResponse resp) {
		resp.setHeader("Access-Control-Allow-Origin", "*");
		resp.setHeader("X-DAS-Status", "200");
		resp.setHeader("X-DAS-Server", "regrest/1.5");
	}

	@RequestMapping(value = "sources/DS_{id}", method = RequestMethod.GET)
	public @ResponseBody
	SOURCES singleSource(@PathVariable("id") String id) {
		return getSingleSource(id);
	}

	@RequestMapping(value = "DS_{id}", method = RequestMethod.GET)
	public @ResponseBody
	SOURCES singleSourceFromRoot(@PathVariable("id") String id) {
		return getSingleSource(id);
	}

	private SOURCES getSingleSource(String id) {
		logger.debug("request for single source " + id);
		SOURCES sources = new SOURCES();

		List<SOURCE> sourcesList = dasSourceManager.getAllSources().getSOURCE();
		for (SOURCE source : sourcesList) {
			if (source.getUri().equals(regPrefix + id)) {

				sources.getSOURCE().add(source);
				return sources;
			}
		}
		return sources;
	}

	private int getDasSpecVersionFromURI(String uri) {
		int version = -1;
		String[] spl = uri.split("/");
		if (spl.length >= 2) {
			for (int i = 0; i < spl.length; i++) {
				String dasString = spl[i];
				if (dasString.equals("das"))
					return 1;
				if (dasString.equals("das1.5"))
					return 15;
				if (dasString.equals("das1.6")) {
					return 16;
				} else if (dasString.equals("das1"))
					return 1;
				else if (dasString.equals("das2"))
					return 2;
			}
		}
		return version;
	}

	public List<String> getParameterValuesAsListFor(HttpServletRequest req,
			String param) {
		List<String> list = new ArrayList<String>();
		String[] values = req.getParameterValues(param);
		if (values != null) {
			for (String value : values) {
				logger.debug("param value=" + value);
			}
			list = Arrays.asList(values);
		}
		return list;
	}

	@RequestMapping(value = "/servers", method = { RequestMethod.GET,
			RequestMethod.HEAD })
	@ResponseStatus(HttpStatus.OK)
	public @ResponseBody
	SERVERS listServers(HttpServletRequest req, HttpServletResponse resp) {
            setHeaders(resp);
		logger.debug("request for servers");
		return dasSourceManager.getServers();
	}
	
}
